package edu.uj.petri.model;

import edu.uj.util.Observer;
import edu.uj.util.ObserverList;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.Set;

/**
 * implementation of Model using standalone objects for all Petri net
 * elements. Incidence matrix is generated only when it is needed.
 *
 * @author Piotrek
 */
class ObjectModel extends Model {
    
    /**
     * matrix where transitions are rows and places are columns
     */
    private transient JamaMatrix incidenceMatrix;
    
    private boolean matrixChanged=true;
    
    private Set<Place> places;
    
    private Set<Transition> transitions;
    
    private int placeCounter=0;
    private int transitionCounter=0;
    
    private Set<Transition> activeTransitions;
    
    private transient ObserverList observerList;
    
    public ObjectModel() {
        incidenceMatrix = new JamaMatrix(0,0);
        places = new HashSet<Place>();
        transitions = new HashSet<Transition>();
        activeTransitions = new HashSet<Transition>();
        observerList = new ObserverList();
    }
    
    /**
     * returns matrix where transitions are rows and places are columns
     */
    public Matrix getIncidenceMatrix() {
        if (matrixChanged) {
            generateIncidenceMatrix();
            matrixChanged = false;
        }
        return incidenceMatrix;
    }
    
    
    /**
     * notifies the object that on of model elements has been changed and
     * incidence matrix will have to be recreated. Will also notify all
     * subscribers of model.
     */
    void modelChanged() {
        matrixChanged = true;
        observerList.notifyAllSubscribers(CHANGED);
    }
    
    /**
     * returns arc weight or 0 if arc has no weight or is null
     */
    private int getArcWeight(Arc arc) {
        if (arc == null)
            return 0;
        if (! (arc instanceof NormalArc))
            return 0;
        NormalArc normArc = (NormalArc)arc;
        return normArc.getWeight();
    }
    
    /**
     * creates incidenceMatrix and sets matrixChanged to false
     */
    private void generateIncidenceMatrix() {
        // TODO test
        incidenceMatrix = new JamaMatrix(transitions.size(), places.size());
        int i=0,j;
        for (Transition t : transitions) {
            j=0;
            for (Place p : places) {
                int weight=0;
                weight += getArcWeight(t.getArcTo(p));
                weight -= getArcWeight(t.getArcFrom(p));
                incidenceMatrix.set(i, j, weight);
                ++j;
            }
            ++i;
        }
    }
    
    /**
     * finds active transitions, remembers them and returns
     */
    public Collection<Transition> findActiveTransitions() {
        activeTransitions.clear();
        for(Transition t : transitions) {
            if (t.canBeFired())
                activeTransitions.add(t);
        }
        return activeTransitions;
    }
    
    /**
     * does not count anything; just returns previously counted
     * active transitions
     * @see {#findActiveTransitions()}
     */
    public Collection<Transition> getActiveTransitions() {
        return activeTransitions;
    }
    
    /**
     * deactivates all active transitions
     */
    public void clearActiveTransitions() {
        for(Transition transition : activeTransitions) {
            transition.deactivate();
        }
        activeTransitions.clear();
    }
    
    public Collection<Transition> getAllTransitions() {
        return transitions;
    }
    
    
    /**
     * creates new place and gives it a name
     */
    public Place createPlace() {
        Place retval = new Place(new PlaceObjectImpl(this));
        retval.setName(ResourceBundle.getBundle("edu/uj/petri/messages").getString("place")+
                " "+Integer.toString(placeCounter++));
        return retval;
    }
    
    /**
     * creates new transition and gives it a name
     */
    public Transition createTransition() {
        Transition retval = new Transition(new TransitionObjectImpl(this));
        retval.setName(ResourceBundle.getBundle("edu/uj/petri/messages").getString("transition")+
                " "+Integer.toString(transitionCounter++));
        return retval;
    }
    
    /**
     * creates new normal arc
     */
    public NormalArc createNormalArc(Node from, Node to) {
        return new NormalArc(from, to, new NormalArcObjectImpl(this));
    }
    
    /**
     * creates new inhibitor arc
     */
    public InhibitorArc createInhibitorArc(Node from, Node to) {
        return new InhibitorArc(from, to, new InhibitorArcObjectImpl(this));
    }
    
    public void deletePlace(Place place) {
        places.remove(place);
        modelChanged();
    }
    
    public void deleteTransition(Transition transition) {
        transitions.remove(transition);
        modelChanged();
    }
    
    /** in this implementation does not do anything */
    public void deleteArc(Arc arc) {
        // ok
    }
    
    // TODO add addArc() and removeArc() ?
    
    public void addPlace(Place place) {
        places.add(place);
        modelChanged();
    }
    
    public void addTransition(Transition transition) {
        transitions.add(transition);
        modelChanged();
    }
    
    /**
     * in this implementation does not do anything; arcs are not stored
     * directly in the model object
     */
    public void addArc(Arc arc) {
        // ok
    }
    
    
    /**
     * returns memento remembering current number of tokens in each place
     */
    public Marking getMarking() {
        ObjectModelMarking retval = new ObjectModelMarking();
        retval.places = new Place[places.size()];
        retval.tokens = new int[places.size()];
        
        int i=0;
        for(Place p : this.places) {
            retval.places[i] = p;
            retval.tokens[i] = p.getTokens();
            ++i;
        }
        
        return retval;
    }
    
    /**
     * restores given number of tokens in places
     */
    public void applyMarking(Marking marking) {
        try {
            ObjectModelMarking m = (ObjectModelMarking)marking;
            int count=m.places.length;
            for(int i=0; i<count; ++i) {
                if (this.places.contains(m.places[i]))
                    m.places[i].setTokens(m.tokens[i]);
            }
        } catch(ClassCastException e) {
            throw new IllegalArgumentException(e.getLocalizedMessage());
        }
    }
    
    public void subscribe(Observer o) {
        observerList.subscribe(o);
    }
    
    public void unsubscribe(Observer o) {
        observerList.unsubscribe(o);
    }
    
    public Collection<Place> getAllPlaces(){
        
        return places;
    }
    
    private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        
        observerList = new ObserverList();
        incidenceMatrix = new JamaMatrix(0,0);
        matrixChanged = true;
    }
    
    
    private static final long serialVersionUID = -7988540049039653818L;
}
